package io.gitee.kotle.android.utils


import androidx.annotation.Keep
import androidx.annotation.WorkerThread
import com.google.gson.annotations.SerializedName
import io.gitee.kotle.android.BaseGlobalConst.okHttpClient
import io.gitee.kotle.android.delegate.unsafeLazy
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import okio.*
import java.io.*
import java.net.URL
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * 发送请求，并等待Response
 */
suspend inline fun Request.awaitHttpResponse(
    client: OkHttpClient = okHttpClient,
    crossinline callBlock: ((Call) -> Unit) = { }
): Response {
    return awaitOnIoThread {
        val call = client.newCall(this)
        callBlock.invoke(call)
        it.invokeOnCancellation { error ->
            call.cancel()
        }
        try {
            it.resume(call.execute())
        } catch (e: Throwable) {
            it.resumeWithException(e)
        }
    }
}

/**
 * 发送请求，并等待转换结果
 */
suspend inline fun <reified T> Request.awaitHttpResult(
    client: OkHttpClient = okHttpClient,
    customType: GsonParameterizedType<T>? = null,
    crossinline callBlock: ((Call) -> Unit) = { },
    crossinline transformer: Function1<Response, T?> = { null }
): T {
    return withIoThread {
        awaitHttpResponse(client, callBlock).fromJson(customType, transformer)
    }
}

/**
 * 结果转换
 */
@WorkerThread
inline fun <reified T> Response.fromJson(
    customType: GsonParameterizedType<T>? = null,
    transformer: Function1<Response, T?> = { null }
): T {
    val result = transformer.invoke(this)
    if (result != null) {
        return result
    }
    //检查错误码
    val oKHttpResponseException = this.code.checkHttpResponseCode()
    if (oKHttpResponseException != null) {
        throw OKHttpResponseException(
            oKHttpResponseException.errorCode,
            "${oKHttpResponseException.message}(${oKHttpResponseException.errorCode})"
        )
    }
    return when (T::class.java) {
        Response::class.java -> this as T
        ResponseBody::class.java -> this.body as T
        String::class.java -> this.body!!.string() as T
        else -> {
            val bodyStr = this.body?.string()
            if (bodyStr.isNullOrBlank()) {
                throw OKHttpResponseException(this.code, this.message)
            } else {
                return bodyStr.formJsonString(customType)
            }
        }
    }
}

/**
 *响应错误
 */
class OKHttpResponseException(var errorCode: Int, msg: String) : Exception(msg)

/**
 *检查响应码，没错错误，返回null
 */
fun Int.checkHttpResponseCode(): OKHttpResponseException? {
    return when (this) {
        200 -> null
        202 -> OKHttpResponseException(this, "服务器已经接收请求，但尚未处理")
        400 -> OKHttpResponseException(this, "服务器不理解请求的语法")
        401 -> OKHttpResponseException(this, "请求身份验证")
        403 -> OKHttpResponseException(this, "服务器拒绝了请求")
        404 -> OKHttpResponseException(this, "服务器找不到请求网页")
        405 -> OKHttpResponseException(this, "服务器禁用了请求指定的方法")
        408 -> OKHttpResponseException(this, "服务器等候请求，发生了超时")
        500 -> OKHttpResponseException(this, "服务器遇到错误，无法去完成请求")
        501 -> OKHttpResponseException(this, "服务器无法识别请求方法")
        502 -> OKHttpResponseException(this, "服务器作为网关或代理，从上游服务器收到无效的响应")
        503 -> OKHttpResponseException(this, "服务器正在升级")
        504 -> OKHttpResponseException(this, "服务器作为网关或代理，没能及时从上游服务器收到请求")
        505 -> OKHttpResponseException(this, "服务器不支持请求所用的http版本协议")
        else -> OKHttpResponseException(this, "未知错误")
    }
}


/**
 * 发起http请求
 */
suspend inline fun <reified T> String.awaitHttp(
    requestBody: RequestBody? = null/*不为null，就代表post请求*/,
    client: OkHttpClient = okHttpClient,
    customType: GsonParameterizedType<T>? = null,
    crossinline callBlock: ((Call) -> Unit) = { },
    requestBuildBlock: ((Request.Builder) -> Request.Builder) = { it },
    crossinline transformer: Function1<Response, T?> = { null }
): T {
    return awaitHttp(
        Request.Builder().url(this),
        requestBody,
        client,
        customType,
        callBlock,
        requestBuildBlock,
        transformer
    )
}

/**
 * 发起http请求
 */
suspend inline fun <reified T> URL.awaitHttp(
    requestBody: RequestBody? = null/*不为null，就代表post请求*/,
    client: OkHttpClient = okHttpClient,
    customType: GsonParameterizedType<T>? = null,
    crossinline callBlock: ((Call) -> Unit) = { },
    requestBuildBlock: ((Request.Builder) -> Request.Builder) = { it },
    crossinline transformer: Function1<Response, T?> = { null }
): T {
    return awaitHttp(
        Request.Builder().url(this),
        requestBody,
        client,
        customType,
        callBlock,
        requestBuildBlock,
        transformer
    )
}

/**
 * 发起http请求
 */
suspend inline fun <reified T> HttpUrl.awaitHttp(
    requestBody: RequestBody? = null/*不为null，就代表post请求*/,
    client: OkHttpClient = okHttpClient,
    customType: GsonParameterizedType<T>? = null,
    crossinline callBlock: ((Call) -> Unit) = { },
    requestBuildBlock: ((Request.Builder) -> Request.Builder) = { it },
    crossinline transformer: Function1<Response, T?> = { null }
): T {
    return awaitHttp(
        Request.Builder().url(this),
        requestBody,
        client,
        customType,
        callBlock,
        requestBuildBlock,
        transformer
    )
}

/**
 * 发起http请求
 */
suspend inline fun <reified T> awaitHttp(
    build: Request.Builder,
    requestBody: RequestBody? = null/*不为null，就代表post请求*/,
    client: OkHttpClient = okHttpClient,
    customType: GsonParameterizedType<T>? = null,
    crossinline callBlock: ((Call) -> Unit) = { },
    requestBuildBlock: ((Request.Builder) -> Request.Builder) = { it },
    crossinline transformer: Function1<Response, T?> = { null }
): T {
    return build
        .also {
            if (requestBody != null) {
                it.post(requestBody)
            } else {
                it.get()
            }
            requestBuildBlock.invoke(it)
        }.build()
        .awaitHttpResult(client, customType, callBlock, transformer)
}

/**
 * 创建RequestBody
 */
fun HashMap<String, String>.toRequestFormBody(): RequestBody {
    val formBody = FormBody.Builder()
    for ((key, value) in this) {
        formBody.add(key, value)
    }
    formBody.build()
    return formBody.build()
}

/**
 * 创建RequestBody
 */
fun MutableMap<String, String>.toRequestFormBody(): RequestBody {
    val formBody = FormBody.Builder()
    for ((key, value) in this) {
        formBody.add(key, value)
    }
    formBody.build()
    return formBody.build()
}

/**
 * 创建RequestBody
 */
fun Any.toRequestJsonBody(): RequestBody {
    val json = when (this) {
        is String -> {
            this
        }
        else -> {
            this.toJsonString()
        }
    }
    return json.toRequestBody("application/json".toMediaTypeOrNull())
}

/**
 * 创建RequestBody
 */
fun ResponseBody?.getStringBody(): String? {
    this ?: return null
    val bufferedReader = BufferedReader(this.charStream())
    return bufferedReader.readText()
}

fun Response?.getStringBody(): String? {
    return this?.body?.getStringBody()
}

/**
 * cookie存储
 */
@Keep
open class SimpleCookieJar : CookieJar {
    @Keep
    class CookieStoreBean {
        @SerializedName("cookies")
        var cookies: List<Cookie>? = null

        @SerializedName("url")
        var url: String? = null
    }

    private val cookieStoreMap = mutableMapOf<String?, CookieStoreBean>()

    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        val host = url.host
        val bean = CookieStoreBean().apply {
            this.url = url.host
            this.cookies = cookies
        }
        cookieStoreMap[host] = bean
        mmkvPutValue(host, bean.toJsonString())
    }

    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        val host = url.host
        var cookieStoreBean = cookieStoreMap[host]
        if (cookieStoreBean == null) {
            mmkvGetValue(host, "").let {
                if (it.isNotBlank()) {
                    val bean = it.formJsonString<CookieStoreBean>()
                    cookieStoreMap[host] = bean
                    cookieStoreBean = bean
                }
            }
        }
        return cookieStoreBean?.cookies ?: ArrayList()
    }
}

/**
 * 对相应体添加进度监听
 */
fun Interceptor.Chain.toProgressResponse(progressListener: Function2<Long, Long, Unit>): Response {
    val originalResponse = proceed(request())
    val body = originalResponse.body ?: return originalResponse
    return originalResponse.newBuilder()
        .body(ProgressResponseBody(body, progressListener))
        .build()
}

/**
 * 下载文件
 */
suspend inline fun OkHttpClient.downloadFile(
    url: String,
    dirPath: String,
    fileName: String,
    byteArraySize: Int = 1024,
    deleteFileIfNoSuccess: Boolean = true,
    crossinline progressListener: (
        downloadLen: Long,
        totalLen: Long,
        progress: Float
    ) -> Unit = { _, _, _ -> },
): File? {
    return downloadFile(
        Request.Builder()
            .url(url)
            .build(),
        dirPath,
        fileName,
        byteArraySize,
        deleteFileIfNoSuccess,
        progressListener
    )
}

/**
 * 下载文件
 */
suspend inline fun OkHttpClient.downloadFile(
    request: Request,
    dirPath: String,
    fileName: String,
    byteArraySize: Int = 1024,
    deleteFileIfNoSuccess: Boolean = true,
    crossinline progressListener: (
        downloadLen: Long,
        totalLen: Long,
        progress: Float
    ) -> Unit = { _, _, _ -> },
): File? {
    val dir = dirPath.trimEnd(File.separatorChar) + File.separatorChar
    val file = File("${dir}${fileName.trimStart(File.separatorChar)}")
    val success = awaitAny<FileOutputStream> {
        val dirFile = File(dir)
        if (!dirFile.exists()) {
            dirFile.mkdirs()
        }
        if (!file.exists()) {
            file.createNewFile()
        }
        it.resume(FileOutputStream(file))
    }.use {
        downloadRequest(
            request, byteArraySize, progressListener
        ) { buf, off, len ->
            it.write(buf, off, len)
        } == null
    }
    //如果没有下载成功，删除文件
    if (deleteFileIfNoSuccess && !success) {
        file.delete()
        return null
    }
    return file
}

/**
 * 监听下载进度,支持协程取消
 */
suspend inline fun OkHttpClient.downloadRequest(
    downRequest: Request,
    byteArraySize: Int = 1024,
    crossinline progressListener: (
        downloadLen: Long,
        totalLen: Long,
        progress: Float
    ) -> Unit = { _, _, _ -> },
    crossinline block: (buf: ByteArray, off: Int, len: Int) -> Unit
): Throwable? {
    return ignoreError(
        errorBlock = {
            doOnlyDebug { it.printStackTrace() }
            it
        }
    ) {
        awaitOnIoThread {
            val call = this@downloadRequest.newCall(downRequest)
            it.invokeOnCancellation { error ->
                call.cancel()
            }
            val response = call.execute()
            val body = response.body
            if (response.code != 200 || body == null) {
                it.resumeWithException(Throwable(response.message))
                return@awaitOnIoThread
            }
            val byteStream = body.byteStream()
            val total = body.contentLength()
            var len: Int
            val buf = ByteArray(byteArraySize)
            var downloadSize = 0L
            while ((byteStream.read(buf).apply { len = this } > 0)) {
                block.invoke(buf, 0, len)
                downloadSize += len

                progressListener.invoke(
                    downloadSize,
                    total,
                    downloadSize * 100f / total
                )

            }
            progressListener.invoke(
                downloadSize,
                total,
                100f
            )
            it.resume(null)
        }
    }
}

//设置了这个拦截可以直接在响应里面读取，否则会读取完网络数据才有响应
open class ProgressResponseBody(
    private val responseBody: ResponseBody,
    val progressListener: Function2<Long, Long, Unit>
) : ResponseBody() {
    private val bufferedSource: BufferedSource by unsafeLazy { source(responseBody.source()).buffer() }

    override
    fun contentType(): MediaType? {
        return responseBody.contentType()
    }

    override
    fun contentLength(): Long {
        return responseBody.contentLength()
    }

    override
    fun source(): BufferedSource {
        return bufferedSource
    }

    private fun source(source: Source): Source {
        return object : ForwardingSource(source) {
            var totalBytesRead = 0L
            override fun read(sink: Buffer, byteCount: Long): Long {
                val bytesRead = super.read(sink, byteCount)
                // read() returns the number of bytes read, or -1 if this source is exhausted.
                totalBytesRead += if (bytesRead != -1L) bytesRead else 0
                progressListener.invoke(totalBytesRead, contentLength())
                return bytesRead
            }
        }
    }
}